﻿// This software is the confidential and proprietary information of NetBay.
// Such confidential information should not be disclosed.
// NetBay copyright(c) 2012

#region

using System;
using System.Collections.Generic;
using System.Linq;
using NetBay.Core.BusinessEntities;
using NetBay.Core.BusinessEntities.Workflow;
using NetBay.Core.BusinessEntities.Workflow.Rules;
using NetBay.Core.Log;
using Rio.Flow.Business.Repository;
using Rio.Workflow.Runtime.Enums;
using Rio.Workflow.Runtime.Exceptions;
using Rio.Workflow.Runtime.Extentions;

#endregion

namespace Rio.Workflow.Runtime
{
    /// <summary>
    /// Base class of all workflow implementation.
    /// </summary>
    public abstract class WorkflowContext : WorkflowContextBase
    {
        #region Readonly & Static Fields

        private readonly object _locker = new object();

        #endregion

        #region Instance Properties

        /// <summary>
        /// Gets the type of the WKF.
        /// </summary>
        /// <value>The type of the WKF.</value>
        public abstract WorkflowType WkfType { get; }

        #endregion

        #region Instance Methods

        /// <summary>
        /// Gets the new state of the workflow.
        /// </summary>
        /// <returns></returns>
        public virtual WorkflowState GetNewWorkflowState()
        {
            return RulesHelper.GetNewWorkflowState(this);
        }

        /// <summary>
        /// Indicates whether if the Instances is finished.
        /// Must overrided to indicate if the instance should be change the step.
        /// </summary>
        /// <returns>Indicates whether if the instance should be close.</returns>
        public virtual bool InstanceIsFinished()
        {
            if (IsCreationMode)
                return false;

            //ActionExecuted rules to determine if the final condition is resolved
            bool result = RulesHelper.WorkflowIsFinalState(this);

            //If all documents have a state only in single validating mode
            if (!result && RulesHelper.GetCurrentAction(this).ValidatingMode == SelectionMode.One)
                result =
                    CurrentInstance.WorkItems.OfType<WorkflowDocument>().All(p => p.DocumentStatus != WorkflowState.None);

            return result;
        }

        /// <summary>
        /// Generates the chrono.
        /// Used by mailing workflow during a chrono request.
        /// </summary>
        /// <returns>The newest chrono item.</returns>
        public override Item RequestChrono(ChronoRequest request)
        {
            return new Item(0, string.Empty);
        }

        /// <summary>
        /// Starts the workflow.
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        /// <param name="context">The context.</param>
        /// <returns>
        /// True if started.
        /// </returns>
        public override WorkflowInstance StartWorkflow(WorkflowDefinition wkfDefinition, CreationContext context)
        {
            WorkflowInstance instance;

            try
            {
                LogManager.Instance().LogDebugFormat("Starting new workflow for definition:{0} with context:{1}",
                                                     wkfDefinition.Identifier, context.ToString());

                //Before wkf start event
                RaiseWorkflowStarting();

                //Create and get the instance
                if (!wkfDefinition.IsHierarchical)
                {
                    //******** Classical workflow
                    instance = WorkflowInstanceRepository.AddAndGetWorkflowInstance(wkfDefinition,
                                                                                    context.FolderIdentifier,
                                                                                    context.UserId,
                                                                                    WorkflowState.Started,
                                                                                    context.StartingCondition,
                                                                                    context.DocumentsIdentifier,
                                                                                    context.ChronoId,
                                                                                    context.ParentInstanceId
                        );
                }
                else
                {
                    //******** Hierarchical workflow
                    List<long> receivers;
                    if (wkfDefinition.IsReceiverAuto())
                    {
                        //Take receivers in the definition
                        receivers = wkfDefinition.ReceiverGroupId;
                    }
                    else
                    {
                        //Take the receivers in the creation context
                        receivers = context.ReceiverGroupId;
                        if (receivers == null)
                        {
                            receivers = wkfDefinition.ReceiverGroupId;
                        }
                    }
                    if (receivers == null)
                    {
                        throw new WorkflowEngineException(Message.NoReceiversFoundedInRules);
                    }

                    instance = WorkflowInstanceRepository.AddAndGetWorkflowInstanceWithReceivers(wkfDefinition,
                                                                                                 context.
                                                                                                     FolderIdentifier,
                                                                                                 context.UserId,
                                                                                                 WorkflowState.Started,
                                                                                                 context.
                                                                                                     StartingCondition,
                                                                                                 context.
                                                                                                     DocumentsIdentifier,
                                                                                                 context.ChronoId,
                                                                                                 context.
                                                                                                     ParentInstanceId,
                                                                                                 receivers
                        );
                }

                if (instance == null)
                {
                    //throw new WorkflowEngineException(Message.WorkflowCreationErrorInstanceNotSet);
                    LogManager.Instance().LogWarn(Message.WorkflowCreationErrorInstanceNotSet);
                }
            }
            catch (WorkflowEngineException ex1)
            {
                LogManager.Instance().LogErrorFormat(" Exception => " + ex1);
                if (CurrentInstance != null)
                {
                    ChangeCurrentInstanceState(WorkflowState.Faulted);
                }

                throw;
            }
            catch (Exception ex)
            {
                LogManager.Instance().LogErrorFormat(" Exception => " + ex);
                if(CurrentInstance != null)
                {
                    ChangeCurrentInstanceState(WorkflowState.Faulted);
                }

                //return null;
                throw new WorkflowEngineException("Unable to start Workflow", ex);
            }


            return instance;
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return Name;
        }

        /// <summary>
        /// Used this method to check if the workflow can start
        /// </summary>
        /// <param name="wkfDefinition">The WKF definition.</param>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public override bool WorkflowCanStart(WorkflowDefinition wkfDefinition, CreationContext context)
        {
            if (wkfDefinition.IsReceiverIndex() && wkfDefinition.IsHierarchical)
            {
                if (context.ReceiverGroupId == null || context.ReceiverGroupId.Count == 0)
                {
                    return false;
                }
            }
            else if (wkfDefinition.IsReceiverIndex() && !wkfDefinition.IsHierarchical)
            {
                //Get receivers 


            }

            return true;
        }

        /// <summary>
        /// Workflows Opened
        /// </summary>
        public override void WorkflowOpened()
        {
        }

        /// <summary>
        /// Starts all childs.
        /// </summary>
        public void StartAllChilds()
        {
            LogManager.Instance().LogDebug(Message.StartChildRequestLog);

            if (!IsActionMode)
            {
                throw new InvalidOperationException(Message.InvalidOperationContext);
            }

            //Get all childs
            List<WorkflowDefinition> definitions =
                WorkflowDefinitionRepository.GetDefinitionsChildToStart(CurrentDefinition.Identifier,
                                                                        CurrentAction.ActionOrConditionId);

            if (definitions.Count > 0)
            {
                //Add inwstance for each childs definitions
                foreach (var item in definitions)
                {
                    CreationContext newContext = new CreationContext
                                                     {
                                                         ChronoId = CurrentInstance.ChronoId,
                                                         FolderIdentifier = CurrentInstance.FolderId,
                                                         StartingCondition = CurrentAction.ActionOrConditionId,
                                                         FolderTypeIdentifier = CurrentDefinition.FolderTypeId,
                                                         ParentInstanceId = CurrentInstance.Identifier,
                                                         UserId = UserId,
                                                         DocumentsIdentifier =
                                                             CurrentInstance.WorkItems.OfType<WorkflowDocument>().
                                                             Select(i => i.DocumentId).ToList()
                                                     };


                    if (StartWorkflow(item, newContext) != null)
                    {
                        LogManager.Instance().LogDebugFormat(Message.NewChildStartedForDefinition, item.ToString());
                    }
                }
            }
        }

        /// <summary>
        /// Changes the state of all document.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="newState">The new state.</param>
        protected void ChangeAllDocumentState(ActionRequest request, WorkflowState newState)
        {
            string comment = string.Empty;
            if (request.Parameters != null)
                comment = request.Parameters[ParameterTokenName.CommentParam].ParameterValue;

            CurrentInstance.ChangeDocumentsState(newState, UserId, RguId, CurrentAction.ActionOrConditionId, comment);
        }

        /// <summary>
        /// Changes the state of all document state with no.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="newState">The new state.</param>
        protected void ChangeAllDocumentStateWithNoState(ActionRequest request, WorkflowState newState)
        {
            string comment = string.Empty;
            if (request.Parameters != null)
                comment = request.Parameters[ParameterTokenName.CommentParam].ParameterValue;

            //Get all documents with no state
            if (CurrentInstance.WorkItems != null)
            {
                var documents =
                    CurrentInstance.WorkItems.Cast<WorkflowDocument>().Where(p => p.DocumentStatus == WorkflowState.None)
                        .ToList();
                foreach (var document in documents)
                {
                    CurrentInstance.ChangeDocumentState(newState, document.Identifier, UserId, RguId,
                                                        CurrentAction.ActionOrConditionId, comment);
                }
            }
        }

        /// <summary>
        /// Changes the state of all document.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="newState">The new state.</param>
        protected void ChangeAllUserDocumentState(ActionRequest request, WorkflowState newState)
        {
            CurrentInstance.ChangeUserDocumentsState(newState);
        }

        /// <summary>
        /// Changes the state of the instance.
        /// </summary>
        /// <param name="newState">The new state.</param>
        /// <param name="isEndState">if set to <c>true</c> [is end state].</param>
        /// <returns></returns>
        protected bool ChangeCurrentInstanceState(WorkflowState newState, bool isEndState = false)
        {
            return CurrentInstance.ChangeInstanceState(newState, UserId, RguId, isEndState);
        }

        /// <summary>
        /// Changes the state of the document.
        /// </summary>
        protected void ChangeDocumentState(ActionRequest request, WorkflowState newState)
        {
            string comment = string.Empty;
            if (request.Parameters != null)
                comment = request.Parameters[ParameterTokenName.CommentParam].ParameterValue;

            foreach (var id in request.wkfDocumentStateIds)
            {
                CurrentInstance.ChangeDocumentState(newState, id, UserId, RguId, CurrentAction.ActionOrConditionId,
                                                    comment);
            }
        }

        /// <summary>
        /// Changes the state of the document.
        /// </summary>
        protected void ChangeUserDocumentState(ActionRequest request, WorkflowState newState)
        {
            foreach (var id in request.wkfDocumentStateIds)
            {
                CurrentInstance.ChangeUserDocumentState(newState, id);
            }
        }

        ///// <summary>
        ///// Changes the state of all parent instance.
        ///// </summary>
        ///// <param name="wkfInstance">The WKF instance.</param>
        ///// <param name="newState">The new state.</param>
        ///// <param name="isEndState">End state of the is.</param>
        //protected void ChangeAllParentInstanceState(WorkflowInstance wkfInstance, WorkflowState newState, bool isEndState = false)
        //{
        //    WorkflowInstance parentInstance = WorkflowInstanceRepository.GetParentWorkflowInstance(wkfInstance.Identifier);

        //    if (parentInstance != null && parentInstance.Identifier > 0)
        //    {
        //        // Get all childs of the parent instance
        //        var childs = WorkflowInstanceRepository.GetChildWorkflowInstance(parentInstance.Identifier);
        //        if (childs != null && childs.Count > 0)
        //        {
        //            if (childs.All(p => p.State == WorkflowState.Finished))
        //            {
        //                // if all childs are finished , the parent can be set to finished
        //                parentInstance.ChangeInstanceState(newState, UserId, RguId, isEndState);
        //                ChangeAllParentInstanceState(parentInstance, newState, isEndState);
        //            }
        //        }
        //    }
        //}

        /// <summary>
        /// Currents the instance has parent.
        /// </summary>
        /// <returns></returns>
        protected bool CurrentInstanceHasParent()
        {
            if (CurrentInstance.ParentId == 0)
                return false;

            return true;
        }

        /// <summary>
        /// Sets the instance to ended.
        /// </summary>
        /// <returns></returns>
        protected bool SetCurrentInstanceToEnded()
        {
            return CurrentInstance.SetInstanceToEnded(true, UserId, RguId);
        }

        /// <summary>
        /// Executes the action.
        /// </summary>
        /// <param name="executor">Action to execute.</param>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        internal override ActionResult ExecuteAction(IActionExecutor executor, ActionRequest request)
        {
            ActionResult result = ActionResult.Executed;

            try
            {
                lock (_locker)
                {
                    LogManager.Instance().LogDebugFormat("ExecuteAction:{0} for the instance:{1}", CurrentAction.Name,
                                                         CurrentInstance.ToString());

                    //*** Executes the custom action
                    if (request.IsGlobalAction)
                    {
                        LogManager.Instance().LogDebug("Executes Global action...");
                        LogManager.Instance().LogDebug("-Context => " + ToString());
                        LogManager.Instance().LogDebug("-Request => " + request.ToString());

                        if (executor != null)
                        {
                            result = executor.ActionExecutingAll(this, request);
                        }

                        //** Execute rules to apply new Documents states
                        LogManager.Instance().LogDebugFormat(Message.StateChangingLog);
                        WorkflowState newDocumentState = RulesHelper.GetNewDocumentState(this);
                        if (newDocumentState != WorkflowState.None)
                        {
                            ChangeAllDocumentState(request, newDocumentState);
                            LogManager.Instance().LogDebugFormat(Message.StateChangedLog);

                            //change documentstate for the userinstance_docstate
                            ChangeAllUserDocumentState(request, newDocumentState);
                        }

                        if (executor != null)
                        {
                            result = executor.ActionExecutedAll(this, request);
                        }
                    }
                    else
                    {
                        LogManager.Instance().LogDebug("Executes Single action...");
                        LogManager.Instance().LogDebug("-Context => " + ToString());
                        LogManager.Instance().LogDebug("-Request => " + request.ToString());

                        if (executor != null)
                        {
                            result = executor.ActionExecuting(this, request);
                        }

                        //** Execute rules to apply new Document state
                        LogManager.Instance().LogDebugFormat(Message.StateChangingLog);
                        WorkflowState newDocumentState = RulesHelper.GetNewDocumentState(this);
                        if (newDocumentState != WorkflowState.None)
                        {
                            ChangeDocumentState(request, newDocumentState);
                            LogManager.Instance().LogDebugFormat(Message.StateChangedLog);

                            //change documentstate for the userinstance_docstate
                            ChangeUserDocumentState(request, newDocumentState);
                        }

                        if (executor != null)
                        {
                            result = executor.ActionExecuted(this, request);
                        }
                    }
                    if (result == ActionResult.Faulted)
                    {
                        throw new Exception(Message.ActionInFaultedState);
                    }


                    //*** Update action for all users in the group 
                    WorkflowInstanceRepository.UpdateReceiversAction(CurrentInstance.Identifier, RguId,
                                                                     CurrentAction.Identifier);

                    //*** Update documents states
                    CurrentInstance.WorkItems = WorkflowInstanceRepository.GetDocumentState(CurrentInstance);


                    //** Apply Workflow new state
                    LogManager.Instance().LogDebugFormat(Message.StateChangingLog);
                    WorkflowState newWkfState = GetNewWorkflowState();
                    if (newWkfState != WorkflowState.None)
                    {
                        if (CurrentInstance.ChangeInstanceStateWithNoUser(newWkfState))
                        {
                            LogManager.Instance().LogDebugFormat(Message.StateChangedLog);
                        }
                    }


                    //*** A workflow instance can be allready finished (transfer, answer step) 
                    if (!CurrentInstance.IsEnded)
                    {
                        //Check if the wkf is hierarchical, add new receiver and change all receivers state    
                        if (CurrentDefinition.IsHierarchical)
                        {
                            var newReceivers = ResolvedNextHierarchicalReceiverId();
                                //RulesHelper.GetActionReceiver(this.CurrentInstance.StartingCondition, this.CurrentAction.ActionOrConditionId);
                            if (newReceivers != null)
                            {
                                //Add new receiver and set old to ended 
                                WorkflowInstanceRepository.UpdateReceiversToEnded(CurrentInstance.Identifier, RguId,
                                                                                  true);
                                WorkflowInstanceRepository.AddReceivers(CurrentInstance.Identifier,
                                                                        new List<long> {Convert.ToInt64(newReceivers)});
                            }
                        }


                        //*** if the current step is finished 
                        if (InstanceIsFinished())
                        {
                            //Start child
                            if (CurrentDefinition.HasChild)
                            {
                                StartAllChilds();
                            }

                            // Check if the new state finish the wkf
                            SetCurrentInstanceToEnded();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Instance().LogErrorFormat(" Exception => " + ex.ToString());
                RaiseWorkflowFaulted();
                throw;
            }

            return result;
        }

        #endregion
    }
}